/*
 * Copyright 2024 ywcai
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

`include "defines.v"
`timescale 1ns/1ps

module pc_reg(
    input	wire					clk,
	input	wire					rst_n,

	input	wire					req_grant_i,
	input	wire					inst_ready_i,
	input	wire[`InstDataBus]		inst_i,
	input	wire					mux_pcsrc_i,
	input	wire[`MemAddrBus]		jump_target_i,
	input	wire					stall_if_req_i,

	output	wire					pp_en_o,
	output	reg						mem_req_o,
	output	wire[`MemAddrBus]		pc_o,
	output	wire[`MemAddrBus]		next_pc_o,
	output	wire[`InstDataBus]		inst_o
	);

	reg					pp_en;

	reg[`MemAddrBus]	if_pc;
	wire[`MemAddrBus]	pc_plus4;
	wire[`MemAddrBus]	pc;
	wire[`MemAddrBus]	next_pc;
	wire[`InstDataBus]	inst;

	reg					preif_valid;
	wire				preif_readygo;
	wire				preif_to_if_valid;
	wire				preif_allowin;

	reg					if_valid;
	wire				if_readygo;
	wire				if_allowin;

	wire				id_allowin;

	reg					keep;
	reg[`MemAddrBus]	pc_keep;
	reg[`MemAddrBus]	next_pc_keep;
	reg[`InstDataBus]	inst_keep;
	reg					throw;
	reg[`MemAddrBus]	pc_hold;

	assign preif_readygo = mem_req_o && req_grant_i;
	assign preif_to_if_valid = preif_readygo;
	assign preif_allowin = !preif_valid || (preif_readygo && if_allowin);

	assign if_readygo = keep || inst_ready_i;
	assign if_allowin = !if_valid || (if_readygo && id_allowin);

	assign id_allowin = !stall_if_req_i;

	assign pp_en_o = pp_en;

	assign pc_plus4 = if_pc + 4;

	assign pc = (!if_valid || throw) ? `ZERO_ADDR
		: (keep ? pc_keep : if_pc);
	assign pc_o = pc;

	assign inst = (!if_valid || throw) ? `INST_NOP
		: (keep ? inst_keep : inst_i);
	assign inst_o = inst;

	assign next_pc = throw ? pc_hold
			: (mux_pcsrc_i ? jump_target_i
			: (keep ? next_pc_keep : pc_plus4));
	assign next_pc_o = next_pc;

	always @(posedge clk) begin
		if (rst_n == `RESET_ENABLE) begin
			pp_en <= `DISABLE;
			if_pc <= `CPU_RESET_ADDR;
			inst_keep <= `INST_NOP;
			mem_req_o <= `DISABLE;
			keep <= `DISABLE;
			preif_valid <= `DISABLE;
			if_valid <= `DISABLE;
			throw <= `DISABLE;
		end else begin
			pp_en <= `ENABLE;

			if (preif_allowin) begin
				preif_valid <= preif_readygo;
			end

			if (if_allowin) begin
				if_valid <= preif_to_if_valid;
			end

			if (if_allowin) begin
				mem_req_o <= `ENABLE;
			end else begin
				mem_req_o <= `DISABLE;
			end

			if (!if_allowin) begin
				if (if_readygo) begin
					keep <= `ENABLE;
					next_pc_keep <= next_pc;
					if (mux_pcsrc_i) begin
						pc_keep <= `ZERO_ADDR;
						inst_keep <= `INST_NOP;
					end else begin
						pc_keep <= if_pc;
						inst_keep <= inst;
					end
				end else if (mux_pcsrc_i) begin
					throw <= `ENABLE;
					pc_hold <= next_pc;
				end
			end else begin
				if (preif_to_if_valid) begin
					keep <= `DISABLE;
					throw <= `DISABLE;
					if_pc <= next_pc;
				end
			end
		end
	end

endmodule
